package org.btik.light.server.dev.manage;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.btik.light.server.platform.common.api.bean.common.StatusResult;
import org.btik.light.server.platform.common.api.bean.user.*;
import org.btik.light.server.platform.common.api.dao.LightDao;
import org.btik.light.server.platform.common.api.service.LoginService;
import org.btik.light.server.platform.common.api.service.SensitiveDataService;
import org.btik.light.server.platform.common.api.service.UserService;
import org.btik.light.server.platform.common.tool.StreamPlus;
import org.btik.light.tool.AESUtil;
import org.btik.light.tool.ByteUtil;
import org.btik.light.tool.DhUtil;
import org.btik.light.tool.MDUtil;
import org.btik.light.tool.type.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.security.InvalidKeyException;
import java.util.Collection;
import java.util.Date;
import java.util.Objects;
import java.util.regex.Pattern;

import static org.btik.light.server.platform.common.api.bean.common.ResultFactory.failed;
import static org.btik.light.server.platform.common.api.bean.common.ResultFactory.success;
import static org.btik.light.server.platform.common.api.bean.user.UserErrorStatus.*;
import static org.btik.light.server.platform.common.tool.PlatformTool.*;

/**
 * @author lustre
 * @since 2023/6/12 17:57
 */
@Service
public class UserServiceImpl implements UserService {

    private final Log log = LogFactory.getLog(UserServiceImpl.class);

    @Autowired
    private LightDao lightDao;

    @Autowired
    private SensitiveDataService sensitiveDataService;

    @Autowired
    private LoginService loginService;

    @Value("${dev.auth.default.user-account}")
    private String adminAccount;


    private final Pattern userNamePattern = Pattern.compile("[a-zA-Z\\d_\\u4e00-\\u9fa5]{1,16}");

    private final Pattern accountPattern = Pattern.compile("[a-zA-Z\\d_]{4,16}");

    private final Pattern passwordPattern = Pattern.compile("^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[$@!%*?&])[A-Za-z\\d$@!%*?&]{6,16}$");

    private static final String[] PASSWORD_SOURCE = new String[]{"0123456789", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "$@!%*?&"};


    @Override
    public Collection<UserVo> list() {
        return StreamPlus.of(lightDao.list(User.class))
                .mapBeanToType(UserVo::new)
                .orderDescBy(UserVo::getCreatTime)
                .toList();
    }

    @Override
    public StatusResult<Void> addUser(AddUserDto addUserDto) {
        String username = addUserDto.getUsername();
        if (StringUtil.isEmpty(username) || !userNamePattern.matcher(username).matches()) {
            return failed(NAME_ILLEGAL);
        }
        String account = addUserDto.getAccount();
        if (StringUtil.isEmpty(account) || !accountPattern.matcher(account).matches()) {
            return failed(ACCOUNT_ILLEGAL);
        }
        String password = addUserDto.getPassword();
        if (StringUtil.isEmpty(password) || !passwordPattern.matcher(password).matches()) {
            return failed(PASSWORD_ILLEGAL);
        }
        User exists = lightDao.get(account, User.class);
        if (exists != null) {
            return failed(ACCOUNT_EXISTS);
        }
        addUserDto.setPassword(MDUtil.digestAsHex(MDUtil.Algorithm.SHA_512_256, account + password));
        User user = map(addUserDto, User::new);
        Date now = new Date();
        user.setCreatTime(now);
        user.setUpdateTime(now);
        lightDao.save(user);
        return success();
    }

    @Override
    public StatusResult<Void> updateUser(UpdateUserDto updateUserDto, UserVo loginUser) {
        // 当前用户仅仅可以更新自己的账号
        if (!loginUser.isAdmin() && !Objects.equals(updateUserDto.getAccount(), loginUser.getAccount())) {
            return failed(NO_PERMISSIONS);
        }

        String username = updateUserDto.getUsername();
        if (StringUtil.isEmpty(username) || !userNamePattern.matcher(username).matches()) {
            return failed(NAME_ILLEGAL);
        }
        String account = updateUserDto.getAccount();
        User dbUser = lightDao.get(account, User.class);
        if (dbUser == null) {
            return failed(ACCOUNT_NOT_EXISTS);
        }
        Date now = new Date();
        dbUser.setUpdateTime(now);
        dbUser.setRemark(updateUserDto.getRemark());
        dbUser.setUsername(updateUserDto.getUsername());
        lightDao.save(dbUser);
        return success();
    }

    @Override
    public StatusResult<Void> unlockUser(String account) {
        User dbUser = lightDao.get(account, User.class);
        if (dbUser == null) {
            return failed(ACCOUNT_NOT_EXISTS);
        }
        if (dbUser.isLocked()) {
            dbUser.setLocked(false);
            dbUser.setFailedCount(0);
            dbUser.setLockTime(0);
            dbUser.setUpdateTime(new Date());
            lightDao.save(dbUser);
        }
        return success();
    }

    @Override
    public StatusResult<Void> changeUserStatus(String account, boolean status) {
        if (adminAccount.equals(account)) {
            return failed(NO_PERMISSIONS);
        }
        User dbUser = lightDao.get(account, User.class);
        if (dbUser == null) {
            return failed(ACCOUNT_NOT_EXISTS);
        }
        if (dbUser.getEnable() != status) {
            dbUser.setEnable(status);
            dbUser.setUpdateTime(new Date());
            lightDao.save(dbUser);
        }
        return success();
    }

    @Override
    public StatusResult<Void> deleteUser(String account) {
        if (adminAccount.equals(account)) {
            return failed(NO_PERMISSIONS);
        }
        User dbUser = lightDao.get(account, User.class);
        if (dbUser == null) {
            return failed(ACCOUNT_NOT_EXISTS);
        }
        lightDao.delete(dbUser.key(), User.class);
        return success();
    }

    @Override
    public StatusResult<String> resetPassword(String account, String publicKey, DhUtil.DhKeyPair dhKeyPair) {

        // 协商得到key 并解密账户名
        byte[] secretKey = sensitiveDataService.exchangeFinal(publicKey, dhKeyPair);
        try {
            byte[] bytes = ByteUtil.hexStrToByteArray(account);
            byte[] decryptAccount = AESUtil.decrypt(AESUtil.ECB_PKCS5Padding, bytes, secretKey);
            if (decryptAccount == null) {
                log.error("decrypt failed");
                return failed(UNKNOWN_EXCEPTION);
            }
            account = new String(decryptAccount);
        } catch (InvalidKeyException e) {
            log.error("", e);
            return failed(UNKNOWN_EXCEPTION);
        }

        if (adminAccount.equals(account)) {
            return failed(NO_PERMISSIONS);
        }

        // 生成密码并保存
        User dbUser = lightDao.get(account, User.class);
        if (dbUser == null) {
            return failed(ACCOUNT_NOT_EXISTS);
        }
        byte[] password = generatePassword(PASSWORD_SOURCE, 8);
        dbUser.setPassword(MDUtil.digestAsHex(MDUtil.Algorithm.SHA_512_256, account + new String(password)));
        lightDao.save(dbUser);

        // 使用协商的key加密密码
        try {
            String result = ByteUtil.encode2Base64Str(AESUtil.encrypt(password, secretKey));
            return success(result);
        } catch (InvalidKeyException e) {
            return failed(UNKNOWN_EXCEPTION);
        }

    }

    @Override
    public StatusResult<Void> modifyPassword(ModifyPasswordDto modifyPasswordDto, String account) {

        String newPassword = modifyPasswordDto.getPassword();

        if (StringUtil.isEmpty(newPassword) || !passwordPattern.matcher(newPassword).matches()) {
            return failed(PASSWORD_ILLEGAL);
        }

        User dbUser = lightDao.get(account, User.class);
        if (dbUser == null) {
            return failed(ACCOUNT_NOT_EXISTS);
        }
        if (loginService.isLocked(dbUser)) {
            return failed(LOCKED);
        }

        // 验证旧密码
        String oldPassword = modifyPasswordDto.getOldPassword();
        String digest = MDUtil.digestAsHex(MDUtil.Algorithm.SHA_512_256, account + oldPassword);
        if (!digest.equals(dbUser.getPassword())) {
            return loginService.onPasswordFailed(dbUser) ? failed(LOCKED) : failed(PASSWORD_FAILED);
        }

        if (Objects.equals(newPassword, oldPassword)) {
            return failed(NEW_PASSWORD_SAME);
        }
        dbUser.setPassword(MDUtil.digestAsHex(MDUtil.Algorithm.SHA_512_256, account + newPassword));
        dbUser.setLocked(false);
        dbUser.setFailedCount(0);
        dbUser.setUpdateTime(new Date());
        lightDao.save(dbUser);

        return success();
    }
}
